// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "Private.h"
#include "Globals.h"
#include "EditSession.h"
#include "SampleIME.h"
#include "CandidateListUIPresenter.h"
#include "CompositionProcessorEngine.h"

//////////////////////////////////////////////////////////////////////
//
// CSampleIME class
//
//////////////////////////////////////////////////////////////////////

//+---------------------------------------------------------------------------
//
// _IsRangeCovered
//
// Returns TRUE if pRangeTest is entirely contained within pRangeCover.
//
//----------------------------------------------------------------------------

BOOL CSampleIME::_IsRangeCovered(TfEditCookie ec, _In_ ITfRange* pRangeTest, _In_ ITfRange* pRangeCover) {
	LONG lResult = 0;;

	if (FAILED(pRangeCover->CompareStart(ec, pRangeTest, TF_ANCHOR_START, &lResult))
		|| (lResult > 0)) {
		return FALSE;
	}

	if (FAILED(pRangeCover->CompareEnd(ec, pRangeTest, TF_ANCHOR_END, &lResult))
		|| (lResult < 0)) {
		return FALSE;
	}

	return TRUE;
}

//+---------------------------------------------------------------------------
//
// _DeleteCandidateList
//
//----------------------------------------------------------------------------

VOID CSampleIME::_DeleteCandidateList(BOOL isForce, _In_opt_ ITfContext* pContext) {
	isForce;pContext;

	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;
	pCompositionProcessorEngine->PurgeVirtualKey();

	if (_pCandidateListUIPresenter) {
		_pCandidateListUIPresenter->_EndCandidateList();

		_candidateMode = CANDIDATE_NONE;
		_isCandidateWithWildcard = FALSE;
	}
}

//+---------------------------------------------------------------------------
//
// _HandleComplete
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleComplete(TfEditCookie ec, _In_ ITfContext* pContext) {
	_DeleteCandidateList(FALSE, pContext);

	// just terminate the composition
	_TerminateComposition(ec, pContext);

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCancel
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCancel(TfEditCookie ec, _In_ ITfContext* pContext) {
	_RemoveDummyCompositionForComposing(ec, _pComposition);

	_DeleteCandidateList(FALSE, pContext);

	_TerminateComposition(ec, pContext);

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionInput
//
// If the keystroke happens within a composition, eat the key and return S_OK.
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionInput(TfEditCookie ec, _In_ ITfContext* pContext, WCHAR wch) {
	ITfRange* pRangeComposition = nullptr;
	TF_SELECTION tfSelection;
	ULONG fetched = 0;
	BOOL isCovered = TRUE;

	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;

	if ((_pCandidateListUIPresenter != nullptr) && (_candidateMode != CANDIDATE_INCREMENTAL)) {
		_HandleCompositionFinalize(ec, pContext, FALSE);
	}

	// Start the new (std::nothrow) compositon if there is no composition.
	if (!_IsComposing()) {
		_StartComposition(pContext);
	}

	// first, test where a keystroke would go in the document if we did an insert
	if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched) != S_OK || fetched != 1) {
		return S_FALSE;
	}

	// is the insertion point covered by a composition?
	if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) {
		isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition);

		pRangeComposition->Release();

		if (!isCovered) {
			goto Exit;
		}
	}

	// Add virtual key to composition processor engine
	pCompositionProcessorEngine->AddVirtualKey(wch);

	_HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext);

Exit:
	tfSelection.range->Release();
	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionInputWorker
//
// If the keystroke happens within a composition, eat the key and return S_OK.
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionInputWorker(_In_ CCompositionProcessorEngine* pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext* pContext) {
	HRESULT hr = S_OK;
	CSampleImeArray<CStringRange> readingStrings;
	BOOL isWildcardIncluded = TRUE;

	//
	// Get reading string from composition processor engine
	//
	pCompositionProcessorEngine->GetReadingStrings(&readingStrings, &isWildcardIncluded);

	for (UINT index = 0; index < readingStrings.Count(); index++) {
		hr = _AddComposingAndChar(ec, pContext, readingStrings.GetAt(index));
		if (FAILED(hr)) {
			return hr;
		}
	}

	//
	// Get candidate string from composition processor engine
	//
	CSampleImeArray<CCandidateListItem> candidateList;

	pCompositionProcessorEngine->GetCandidateList(&candidateList, TRUE, FALSE);

	if ((candidateList.Count())) {
		hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext);
		if (SUCCEEDED(hr)) {
			_pCandidateListUIPresenter->_ClearList();
			_pCandidateListUIPresenter->_SetText(&candidateList, TRUE);
		}
	} else if (_pCandidateListUIPresenter) {
		_pCandidateListUIPresenter->_ClearList();
	} else if (readingStrings.Count() && isWildcardIncluded) {
		hr = _CreateAndStartCandidate(pCompositionProcessorEngine, ec, pContext);
		if (SUCCEEDED(hr)) {
			_pCandidateListUIPresenter->_ClearList();
		}
	}
	return hr;
}
//+---------------------------------------------------------------------------
//
// _CreateAndStartCandidate
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_CreateAndStartCandidate(_In_ CCompositionProcessorEngine* pCompositionProcessorEngine, TfEditCookie ec, _In_ ITfContext* pContext) {
	HRESULT hr = S_OK;

	if (((_candidateMode == CANDIDATE_PHRASE) && (_pCandidateListUIPresenter))
		|| ((_candidateMode == CANDIDATE_NONE) && (_pCandidateListUIPresenter))) {
		// Recreate candidate list
		_pCandidateListUIPresenter->_EndCandidateList();
		delete _pCandidateListUIPresenter;
		_pCandidateListUIPresenter = nullptr;

		_candidateMode = CANDIDATE_NONE;
		_isCandidateWithWildcard = FALSE;
	}

	if (_pCandidateListUIPresenter == nullptr) {
		_pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow,
			CATEGORY_CANDIDATE,
			pCompositionProcessorEngine->GetCandidateListIndexRange(),
			FALSE);
		if (!_pCandidateListUIPresenter) {
			return E_OUTOFMEMORY;
		}

		_candidateMode = CANDIDATE_INCREMENTAL;
		_isCandidateWithWildcard = FALSE;

		// we don't cache the document manager object. So get it from pContext.
		ITfDocumentMgr* pDocumentMgr = nullptr;
		if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr))) {
			// get the composition range.
			ITfRange* pRange = nullptr;
			if (SUCCEEDED(_pComposition->GetRange(&pRange))) {
				hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth());
				pRange->Release();
			}
			pDocumentMgr->Release();
		}
	}

	return hr;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionFinalize
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionFinalize(TfEditCookie ec, _In_ ITfContext* pContext, BOOL isCandidateList) {
	HRESULT hr = S_OK;

	if (isCandidateList && _pCandidateListUIPresenter) {
		// Finalize selected candidate string from CCandidateListUIPresenter
		DWORD_PTR candidateLen = 0;
		const WCHAR* pCandidateString = nullptr;

		candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString);

		CStringRange candidateString;
		candidateString.Set(pCandidateString, candidateLen);

		if (candidateLen) {
			// Finalize character
			hr = _AddCharAndFinalize(ec, pContext, &candidateString);
			if (FAILED(hr)) {
				return hr;
			}
		}
	} else {
		// Finalize current text store strings
		if (_IsComposing()) {
			ULONG fetched = 0;
			TF_SELECTION tfSelection;

			if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1) {
				return S_FALSE;
			}

			ITfRange* pRangeComposition = nullptr;
			if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) {
				if (_IsRangeCovered(ec, tfSelection.range, pRangeComposition)) {
					_EndComposition(pContext);
				}

				pRangeComposition->Release();
			}

			tfSelection.range->Release();
		}
	}

	_HandleCancel(ec, pContext);

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionConvert
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionConvert(TfEditCookie ec, _In_ ITfContext* pContext, BOOL isWildcardSearch) {
	HRESULT hr = S_OK;

	CSampleImeArray<CCandidateListItem> candidateList;

	//
	// Get candidate string from composition processor engine
	//
	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;
	pCompositionProcessorEngine->GetCandidateList(&candidateList, FALSE, isWildcardSearch);

	// If there is no candlidate listin the current reading string, we don't do anything. Just wait for
	// next char to be ready for the conversion with it.
	int nCount = candidateList.Count();
	if (nCount) {
		if (_pCandidateListUIPresenter) {
			_pCandidateListUIPresenter->_EndCandidateList();
			delete _pCandidateListUIPresenter;
			_pCandidateListUIPresenter = nullptr;

			_candidateMode = CANDIDATE_NONE;
			_isCandidateWithWildcard = FALSE;
		}

		// 
		// create an instance of the candidate list class.
		// 
		if (_pCandidateListUIPresenter == nullptr) {
			_pCandidateListUIPresenter = new (std::nothrow) CCandidateListUIPresenter(this, Global::AtomCandidateWindow,
				CATEGORY_CANDIDATE,
				pCompositionProcessorEngine->GetCandidateListIndexRange(),
				FALSE);
			if (!_pCandidateListUIPresenter) {
				return E_OUTOFMEMORY;
			}

			_candidateMode = CANDIDATE_ORIGINAL;
		}

		_isCandidateWithWildcard = isWildcardSearch;

		// we don't cache the document manager object. So get it from pContext.
		ITfDocumentMgr* pDocumentMgr = nullptr;
		if (SUCCEEDED(pContext->GetDocumentMgr(&pDocumentMgr))) {
			// get the composition range.
			ITfRange* pRange = nullptr;
			if (SUCCEEDED(_pComposition->GetRange(&pRange))) {
				hr = _pCandidateListUIPresenter->_StartCandidateList(_tfClientId, pDocumentMgr, pContext, ec, pRange, pCompositionProcessorEngine->GetCandidateWindowWidth());
				pRange->Release();
			}
			pDocumentMgr->Release();
		}
		if (SUCCEEDED(hr)) {
			_pCandidateListUIPresenter->_SetText(&candidateList, FALSE);
		}
	}

	return hr;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionBackspace
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionBackspace(TfEditCookie ec, _In_ ITfContext* pContext) {
	ITfRange* pRangeComposition = nullptr;
	TF_SELECTION tfSelection;
	ULONG fetched = 0;
	BOOL isCovered = TRUE;

	// Start the new (std::nothrow) compositon if there is no composition.
	if (!_IsComposing()) {
		return S_OK;
	}

	// first, test where a keystroke would go in the document if we did an insert
	if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) || fetched != 1) {
		return S_FALSE;
	}

	// is the insertion point covered by a composition?
	if (SUCCEEDED(_pComposition->GetRange(&pRangeComposition))) {
		isCovered = _IsRangeCovered(ec, tfSelection.range, pRangeComposition);

		pRangeComposition->Release();

		if (!isCovered) {
			goto Exit;
		}
	}

	//
	// Add virtual key to composition processor engine
	//
	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;

	DWORD_PTR vKeyLen = pCompositionProcessorEngine->GetVirtualKeyLength();

	if (vKeyLen) {
		pCompositionProcessorEngine->RemoveVirtualKey(vKeyLen - 1);

		if (pCompositionProcessorEngine->GetVirtualKeyLength()) {
			_HandleCompositionInputWorker(pCompositionProcessorEngine, ec, pContext);
		} else {
			_HandleCancel(ec, pContext);
		}
	}

Exit:
	tfSelection.range->Release();
	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionArrowKey
//
// Update the selection within a composition.
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionArrowKey(TfEditCookie ec, _In_ ITfContext* pContext, KEYSTROKE_FUNCTION keyFunction) {
	ITfRange* pRangeComposition = nullptr;
	TF_SELECTION tfSelection;
	ULONG fetched = 0;

	// get the selection
	if (FAILED(pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched))
		|| fetched != 1) {
		// no selection, eat the keystroke
		return S_OK;
	}

	// get the composition range
	if (FAILED(_pComposition->GetRange(&pRangeComposition))) {
		goto Exit;
	}

	// For incremental candidate list
	if (_pCandidateListUIPresenter) {
		_pCandidateListUIPresenter->AdviseUIChangedByArrowKey(keyFunction);
	}

	pContext->SetSelection(ec, 1, &tfSelection);

	pRangeComposition->Release();

Exit:
	tfSelection.range->Release();
	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionPunctuation
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionPunctuation(TfEditCookie ec, _In_ ITfContext* pContext, WCHAR wch) {
	HRESULT hr = S_OK;

	if (_candidateMode != CANDIDATE_NONE && _pCandidateListUIPresenter) {
		DWORD_PTR candidateLen = 0;
		const WCHAR* pCandidateString = nullptr;

		candidateLen = _pCandidateListUIPresenter->_GetSelectedCandidateString(&pCandidateString);

		CStringRange candidateString;
		candidateString.Set(pCandidateString, candidateLen);

		if (candidateLen) {
			_AddComposingAndChar(ec, pContext, &candidateString);
		}
	}
	//
	// Get punctuation char from composition processor engine
	//
	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;

	WCHAR punctuation = pCompositionProcessorEngine->GetPunctuation(wch);

	CStringRange punctuationString;
	punctuationString.Set(&punctuation, 1);

	// Finalize character
	hr = _AddCharAndFinalize(ec, pContext, &punctuationString);
	if (FAILED(hr)) {
		return hr;
	}

	_HandleCancel(ec, pContext);

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _HandleCompositionDoubleSingleByte
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_HandleCompositionDoubleSingleByte(TfEditCookie ec, _In_ ITfContext* pContext, WCHAR wch) {
	HRESULT hr = S_OK;

	WCHAR fullWidth = Global::FullWidthCharTable[wch - 0x20];

	CStringRange fullWidthString;
	fullWidthString.Set(&fullWidth, 1);

	// Finalize character
	hr = _AddCharAndFinalize(ec, pContext, &fullWidthString);
	if (FAILED(hr)) {
		return hr;
	}

	_HandleCancel(ec, pContext);

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _InvokeKeyHandler
//
// This text service is interested in handling keystrokes to demonstrate the
// use the compositions. Some apps will cancel compositions if they receive
// keystrokes while a compositions is ongoing.
//
// param
//    [in] uCode - virtual key code of WM_KEYDOWN wParam
//    [in] dwFlags - WM_KEYDOWN lParam
//    [in] dwKeyFunction - Function regarding virtual key
//----------------------------------------------------------------------------

HRESULT CSampleIME::_InvokeKeyHandler(_In_ ITfContext* pContext, UINT code, WCHAR wch, DWORD flags, _KEYSTROKE_STATE keyState) {
	flags;

	CKeyHandlerEditSession* pEditSession = nullptr;
	HRESULT hr = E_FAIL;

	// we'll insert a char ourselves in place of this keystroke
	pEditSession = new (std::nothrow) CKeyHandlerEditSession(this, pContext, code, wch, keyState);
	if (pEditSession == nullptr) {
		goto Exit;
	}

	//
	// Call CKeyHandlerEditSession::DoEditSession().
	//
	// Do not specify TF_ES_SYNC so edit session is not invoked on WinWord
	//
	hr = pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr);

	pEditSession->Release();

Exit:
	return hr;
}
